using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace PatternMaker
{
	public class CrossStitchPatternGenerator : PatternGenerator
	{
		public int colourChartItemHeight = 30;
		public char[,] symbolChart;

		override protected void Execute()
		{
			InnerPattern = (Bitmap)Original.Clone();
			InnerPatternThumbnail = (Bitmap)Original.Clone();

			decimal totalCells = (InnerPattern.Width / CellWidth) * (InnerPattern.Height / CellHeight);
			decimal increment = 100M / totalCells;
			decimal cellCount = 0;
			Rectangle cellRectangle = new Rectangle();
			cellRectangle.Height = CellHeight;
			cellRectangle.Width = CellWidth;
			char code;
			int cellX = 0;
			int cellY;

			if (ParametersChanged || symbolChart == null)
			{
				symbolChart = new char[CellCountHorizontal, CellCountVertical];
				ColourCodes.Clear();

				for (int x = 0; x < InnerPattern.Width; x += CellWidth)
				{
					cellY = 0;
					for (int y = 0; y < InnerPattern.Height; y += CellHeight)
					{
						cellRectangle.X = x;
						cellRectangle.Y = y;
						code = NormalizeColour(cellRectangle);
						if (cellX < symbolChart.GetLength(0) && cellY < symbolChart.GetLength(1))
						{
							symbolChart[cellX, cellY] = code;
						}
						cellCount++;
						OnProgressChanged(new ProgressEventArgs(((int)(increment * cellCount))));
						cellY++;
					}
					cellX++;
				}

				// create colour chart
				ColourChart = new Bitmap(100, colourChartItemHeight * ColourCodes.Count);
				int i = 0;
				Graphics g = Graphics.FromImage(ColourChart);
				g.FillRectangle(new SolidBrush(Color.White), g.ClipBounds);
				Brush b = new SolidBrush(Color.Black);
				Color colour;
				foreach (int argb in ColourCodes.Keys)
				{
					colour = Color.FromArgb(argb);
					code = ColourCodes[argb];
					g.DrawString(new string(new char[] { code }), Font, b, 10, i * colourChartItemHeight);
					g.FillRectangle(new SolidBrush(colour), 20, i * colourChartItemHeight, 20, colourChartItemHeight - 10);
					i++;
				}
			}
			else
			{
				InnerPattern = (Bitmap)Original.Clone();
				Graphics g = Graphics.FromImage(InnerPattern);
				for (int x = 0; x < InnerPattern.Width; x += CellWidth)
				{
					cellY = 0;
					for (int y = 0; y < InnerPattern.Height; y += CellHeight)
					{
						cellRectangle.X = x;
						cellRectangle.Y = y;
						g.FillRectangle(new SolidBrush(Color.White), cellRectangle);
						//Font f = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
						Font f = Font;
						if (cellX < symbolChart.GetLength(0) && cellY < symbolChart.GetLength(1))
						{
							code = symbolChart[cellX, cellY];
							g.DrawString(new string(new char[] { code }), f, new SolidBrush(Color.Black), cellRectangle.Left, cellRectangle.Top);
						}
						cellCount++;
						OnProgressChanged(new ProgressEventArgs(((int)(increment * cellCount))));
						cellY++;
					}
					cellX++;
				}
				g.Flush();
			}

			InnerPattern = ScaleImage(InnerPattern, Scale);
			ApplyGrid(InnerPattern);
		}

		private char GetColourCode(ref Color color)
		{
			char code = (char)0;

			bool found = false;
			int rtolerance;
			int gtolerance;
			int btolerance;

			byte findA = color.A;
			byte findR = color.R;
			byte findG = color.G;
			byte findB = color.B;

			foreach (int argb in ColourCodes.Keys)
			{
				byte a = Color.FromArgb(argb).A;
				byte r = Color.FromArgb(argb).R;
				byte g = Color.FromArgb(argb).G;
				byte b = Color.FromArgb(argb).B;

				bool foundR = false;
				rtolerance = 0;
				do
				{
					if (r == findR - rtolerance || r == findR + rtolerance)
					{
						foundR = true;
					}
					else rtolerance++;
				}
				while (!foundR && rtolerance <= ColourTolerance);

				bool foundG = false;
				gtolerance = 0;
				do
				{
					if (g == findG - gtolerance || g == findG + gtolerance)
					{
						foundG = true;
					}
					else gtolerance++;
				}
				while (!foundG && gtolerance <= ColourTolerance);

				bool foundB = false;
				btolerance = 0;
				do
				{
					if (b == findB - btolerance || b == findB + btolerance)
					{
						foundB = true;
					}
					else btolerance++;
				}
				while (!foundB && btolerance <= ColourTolerance);

				if (foundR && foundG && foundB)
				{
					code = ColourCodes[argb];
					color = Color.FromArgb(argb);
					found = true;
					break;
				}
			}

			if (!found)
			{
				code = (char)(ColourCodes.Count + 65);
				int argb = color.ToArgb();
				ColourCodes[argb] = code;
			}

			return code;
		}

		private char NormalizeColour(Rectangle r)
		{
			// get colour counts
			Dictionary<Color, int> colourCounts = new Dictionary<Color, int>();
			Color pixelColour;
			for (int x = r.Left; x < r.Right && x < Original.Width; x++)
			{
				for (int y = r.Top; y < r.Bottom && y < Original.Height; y++)
				{
					pixelColour = Original.GetPixel(x, y);
					if (colourCounts.ContainsKey(pixelColour)) colourCounts[pixelColour]++;
					else colourCounts[pixelColour] = 1;
				}
			}

			// pick colour with highest frequency
			Color bestColour = Color.White;
			int bestCount = 0;
			foreach (Color keyColour in colourCounts.Keys)
			{
				if (colourCounts[keyColour] > bestCount)
				{
					bestColour = keyColour;
					bestCount = colourCounts[keyColour];
				}
			}

			// add colour to colour codes
			char code = GetColourCode(ref bestColour);

			// thumbnail, fill cell with normalized colour
			Graphics g = Graphics.FromImage(InnerPatternThumbnail);
			g.FillRectangle(new SolidBrush(bestColour), r);
			g.Flush();

			g = Graphics.FromImage(InnerPattern);
			g.FillRectangle(new SolidBrush(Color.White), r);
			//Font f = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
			Font f = Font;
			g.DrawString(new string(new char[] { code }), f, new SolidBrush(Color.Black), r.Left, r.Top);
			g.Flush();

			return code;
		}

		public override void Preview()
		{
			InnerPattern = (Bitmap)Original.Clone();
			InnerPattern = ScaleImage(InnerPattern, Scale);
			ApplyGrid(InnerPattern);
		}
	}
}
